/*
 * Copyright ©2021 su binglun(9085309@qq.com). All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *---------------------------------------------------------------------
 *-                                                                   -
 *-           Website : http://phoenix.sunny360.cn/                   -
 *-                                                                   -
 *---------------------------------------------------------------------
 */
package cn.sunny360.core.service.impl;

import cn.sunny360.core.common.PhResult;
import cn.sunny360.core.service.UserService;
import cn.sunny360.core.utils.Container;
import cn.sunny360.core.wrapper.CacheWrapper;
import cn.sunny360.core.wrapper.DBWrapper;
import cn.sunny360.messsage.MsgMessage;
import com.sohu.idcenter.IdWorker;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Tuple;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.mindrot.jbcrypt.BCrypt;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * implementation of {@link UserService}.
 *
 * @author sublun
 */
public class UserServiceImpl implements UserService {

	private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

	private IdWorker idWorker;
	private DBWrapper dbWrapper;
	private CacheWrapper cacheWrapper;

	public UserServiceImpl() {
		idWorker = Container.getComponent(IdWorker.class);
		dbWrapper = Container.getComponent(DBWrapper.class);
		cacheWrapper = Container.getComponent(CacheWrapper.class);
	}

	@Override
	public UserServiceImpl doLogin(JsonObject jsonUser, Promise<JsonObject> resultHandler) {
		logger.debug(jsonUser);
		String username = jsonUser.getString("username");
		String password = jsonUser.getString("password");
		String token = jsonUser.getString("token");
		Promise<Optional<JsonObject>> userPromise = dbWrapper
				.findOne("select * from ph_user where username = ?", Tuple.of(username));
		userPromise.future().onFailure(resultHandler::fail)
				.onSuccess(option->{

					//没有查询到数据
					if (!option.isPresent()) {
						resultHandler.fail("用户名或密码错误！");
						return;
					}
					//取用户信息
					JsonObject user = option.get();
					String pwd = user.getString("password");
					//判断密码是否正确
					if (!BCrypt.checkpw(password, pwd)) {
						resultHandler.handle(Future.failedFuture("用户名或密码错误！"));
						return;
					}
					logger.info("密码认证成功。");
					// 生成token
					// 判断用户是否有token，如果没有生成token
					if (StringUtils.isBlank(token)) {
						user.put("token", idWorker.getId() + "");
					} else {
						user.put("token", token);
					}
					//查询用户头像 TODO 暂时使用id，不用查询头像url
					/*Long imageId = user.getLong("head_img");
					if (imageId != null) {
						Promise<JsonObject> imageResult = Promise.promise();
						getImageById(imageId, imageResult);
						imageResult.future()
								.onFailure(e->{
									logger.error("查询用户头像发生异常", e);
									saveToken(user, resultHandler);
								}).onSuccess(imageInfo->{
									user.put("avatar", imageInfo.getString("url"));
									saveToken(user, resultHandler);
								});
					} else {
						saveToken(user, resultHandler);
					}*/
					saveToken(user, resultHandler);
				});

		return this;
	}

	private void getImageById(Long id, Promise<JsonObject> imageResult) {
		dbWrapper.findOne("select * from ph_images where image_id = ?", Tuple.of(id))
				.future()
				.onFailure(imageResult::fail)
				.onSuccess(option->{
					if (!option.isPresent()) {
						imageResult.complete(new JsonObject());
					} else {
						imageResult.complete(option.get());
					}
				});
	}

	private void saveToken(JsonObject user, Promise<JsonObject> handler) {
		//查询token信息
		Promise<Optional<JsonObject>> promise = dbWrapper
				.findOne("select * from ph_user_token where user_id = ?", Tuple.of(user.getLong("user_id")));
		promise.future().onFailure(handler::fail)
				.onSuccess(option->{
					//没有查询到数据
					if (!option.isPresent()) {
						user.put("last_version", 0);
						//插入用户token信息
						dbWrapper.insert("insert into " +
											"ph_user_token(" +
											"	user_id, " +
											"	token, " +
											"	last_version, " +
											"	comment) " +
											"values(?,?,?,?)"
								, Tuple.of(user.getLong("user_id"), user.getString("token"), 0, null));
					} else {
						JsonObject userToken = option.get();
						user.put("last_version", userToken.getLong("last_version"));
						//判断是否要更新token
						if (!userToken.getString("token")
								.equals(user.getString("token"))) {
							dbWrapper.update("update ph_user_token set token = ? where user_id = ?",
									Tuple.of(user.getString("token"), user.getLong("user_id")));
						}
					}
					cacheWrapper.getToken("t:"+user.getLong("user_id"), result->{
						if (result.succeeded()) {
							String token = (String) result.result();
							//创建一个关闭连接的消息
							MsgMessage message = MsgMessage.newInstance()
									.setType("close")
									.setTime(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
							//关闭连接
							Container.vertx().eventBus().send(token, Buffer.buffer(message.toByteArray()));
						}
						//缓存token
						cacheWrapper.set("t:"+user.getLong("user_id"), user.getString("token"));
						handler.complete(user);
					});
				});
	}

	@Override
	public UserServiceImpl wsConnection(Long userId, String token,
                                        Promise<Boolean> resultHandler) {

		//根据用户id查询token信息
		Promise<Optional<JsonObject>> promise = dbWrapper
				.findOne("select * from ph_user_token where user_id = ?", Tuple.of(userId));
		promise.future()
				.onFailure(resultHandler::fail)
				.onSuccess(optional->{
					if (!optional.isPresent()) {
						//用户没有token信息，从未登录过
						resultHandler.fail("用户未登录，拒绝连接");
						return;
					}
					//取用户信息
					JsonObject userToken = optional.get();
					if (!token.equals(userToken.getString("token"))) {
						resultHandler.fail("token不正确");
						return;
					}
					//添加到在线用户列表
					Promise<Boolean> p = Promise.promise();
					addOnlineList(userToken, p);
					p.future()
							.onFailure(resultHandler::fail)
							.onSuccess(r->resultHandler.complete(true));
				});
		return this;
	}

	private void addOnlineList(JsonObject userToken, Promise<Boolean> promise) {
		Promise<Boolean> result = dbWrapper.executeWithTrans(Arrays.asList(
				new DBWrapper.SQLTuple("delete from ph_online where user_id=?",
						Tuple.of(userToken.getLong("user_id"))),
				new DBWrapper.SQLTuple("insert into " +
				"ph_online(" +
				"	user_id," +
				"	token," +
				"	comment) " +
				"values" +
				"	(?,?,?)", Tuple.of(
				userToken.getLong("user_id"),
				userToken.getString("token"),
				userToken.getString("comment"))
		)));
		result.future()
				.onFailure(promise::fail)
				.onSuccess(promise::complete);
	}

	@Override
	public UserServiceImpl getTokenByUserId(Long userId, Promise<String> resultHandler) {

		// 根据用户id查询token
		Promise<Optional<JsonObject>> promise = dbWrapper
				.findOne("select token from ph_user_token where user_id = ?", Tuple.of(userId));
		promise.future()
				.onFailure(resultHandler::fail)
				.onSuccess(optional->{
					if (!optional.isPresent()) {
						resultHandler.fail("没有查询到数据");
						return;
					}
					JsonObject token = optional.get();
					resultHandler.complete(token.getString("token"));
				});
		return this;
	}


	@Override
	public UserServiceImpl getOnlineUserList(Long userId, Promise<JsonArray> resultHandler) {
		Promise<List<JsonObject>> promise = dbWrapper.findList(
				"SELECT\n" +
						"	a.* \n" +
						"FROM\n" +
						"	ph_user_linkman a\n" +
						"	JOIN ph_online b ON a.linkman_id = b.user_id \n" +
						"WHERE\n" +
						"	a.owner_id = ?", Tuple.of(userId));
		promise.future()
				.onFailure(resultHandler::fail)
				.onSuccess(userList->{
					JsonArray jsonList = new JsonArray();
					userList.forEach(jsonList::add);
					resultHandler.complete(jsonList);
				});
		return this;
	}

	@Override
	public UserServiceImpl wsDisconnection(String token, Promise<String> resultHandler) {

		// 根据token查询用户
		Promise<Optional<JsonObject>> promise = dbWrapper
				.findOne("select * from ph_online where token = ?", Tuple.of(token));
		promise.future().onFailure(resultHandler::fail)
				.onSuccess(optional->{
					if(!optional.isPresent()) {
						resultHandler.fail("用户已经不在线了");
						return;
					}
					JsonObject onlineUser = optional.get();
					Promise<Boolean> trans = dbWrapper.executeWithTrans(Arrays.asList(
							new DBWrapper.SQLTuple("delete from ph_online where user_id = ?", Tuple.of(onlineUser.getLong("user_id")))
					));
					trans.future()
							.onFailure(resultHandler::fail)
							.onSuccess(r->{
								//删除缓存
								cacheWrapper.remove("t:" + onlineUser.getLong("user_id"));
								resultHandler.complete("OK");
							});
				});
		return this;
	}


	/**
	 * 判断当前用户是否在线，如果在线返回用户的token，如果用户不在线返回“offline”字符串
	 * @param userId
	 * @param resultHandler
	 * @return
	 */
	@Override
	public UserServiceImpl checkOnlineStatus(Long userId, Promise<String> resultHandler) {
		// 判断用户在在线用户列表中是否存在
		dbWrapper.findOne("select * from ph_online where user_id = ?", Tuple.of(userId))
				.future()
				.onFailure(resultHandler::fail)
				.onSuccess(o->{
					if (!o.isPresent()) {
						resultHandler.complete("offline");
						return;
					}
					JsonObject jsonObject = o.get();
					String token = jsonObject.getString("token");
					resultHandler.complete(token);
				});
		return this;
	}

	@Override
	public UserServiceImpl updateLastVersion(Long userId, Long version,
                                             Promise<Long> resultHandler) {
		dbWrapper.update("update ph_user_token set last_version = ? where user_id = ?", Tuple.of(version, userId))
				.future()
				.onFailure(resultHandler::fail)
				.onSuccess(h->resultHandler.complete((long) h));
		return this;
	}

	@Override
	public UserServiceImpl getUserInfoById(Long userId, Promise<JsonObject> handler) {
		dbWrapper.findOne(
					"SELECT\n" +
						"	a.user_id,\n" +
						"	b.role_name,\n" +
						"	a.username,\n" +
						"	a.nick_name,\n" +
						"	a.phone,\n" +
						"	a.email,\n" +
						"	a.sex,\n" +
						"	a.birthday,\n" +
						"	a.address,\n" +
						"	c.url head_img,\n" +
						"	d.url back_img,\n" +
						"	a.last_login_time,\n" +
						"	a.user_desc \n" +
						"FROM\n" +
						"	ph_user a\n" +
						"	LEFT JOIN ph_role b ON a.role_id = b.role_id\n" +
						"	LEFT JOIN ph_images c ON a.head_img = c.image_id\n" +
						"	LEFT JOIN ph_images d ON a.back_img = c.image_id \n" +
						"WHERE\n" +
						"	a.user_id =?", Tuple.of(userId))
				.future()
				.onFailure(handler::fail)
				.onSuccess(o->{
					if (!o.isPresent()) {
						handler.fail("用户不存在！");
					} else {
						JsonObject jsonObject = o.get();
						handler.complete(jsonObject);
					}
				});

		return this;
	}

	@Override
	public UserServiceImpl getUserInfoByName(String username, Promise<JsonObject> resultHandler) {
		dbWrapper.findOne(
				"SELECT\n" +
						"	a.user_id,\n" +
						"	b.role_name,\n" +
						"	a.username,\n" +
						"	a.nick_name,\n" +
						"	a.phone,\n" +
						"	a.email,\n" +
						"	a.sex,\n" +
						"	a.birthday,\n" +
						"	a.address,\n" +
						"	c.url head_img,\n" +
						"	d.url back_img,\n" +
						"	a.last_login_time,\n" +
						"	a.user_desc, \n" +
						"	e.token, \n" +
						"	e.last_version, \n" +
						"	e.comment \n" +
						"FROM\n" +
						"	ph_user a\n" +
						"	LEFT JOIN ph_role b ON a.role_id = b.role_id\n" +
						"	LEFT JOIN ph_images c ON a.head_img = c.image_id\n" +
						"	LEFT JOIN ph_images d ON a.back_img = c.image_id \n" +
						"	LEFT JOIN ph_user_token e ON a.user_id = e.user_id \n" +
						"WHERE\n" +
						"	a.username =?", Tuple.of(username))
				.future()
				.onFailure(resultHandler::fail)
				.onSuccess(o->{
					if (!o.isPresent()) {
						resultHandler.fail("用户不存在！");
					} else {
						JsonObject jsonObject = o.get();
						resultHandler.complete(jsonObject);
					}
				});
		return this;
	}

	@Override
	public UserService createUser(JsonObject user, Promise<PhResult> resultHandler) {
		long userId = idWorker.getId();
		String password = user.getString("password");
		password = BCrypt.hashpw(password, BCrypt.gensalt());
		dbWrapper.insert(
				"insert into ph_user(\n" +
				"  user_id,\n" +
				"  role_id,\n" +
				"  username,\n" +
				"  nick_name,\n" +
				"  password,\n" +
				"  phone,\n" +
				"  email,\n" +
				"  sex,\n" +
				"  birthday,\n" +
				"  address,\n" +
				"  head_img,\n" +
				"  back_img,\n" +
				"  last_login_time,\n" +
				"  user_desc,\n" +
				"  credit_level,\n" +
				"  create_time,\n" +
				"  update_time,\n" +
				"  verify_type\n" +
				") values (\n" +
				"  ?,?,?,?,?,\n" +
				"  ?,?,?,?,?,\n" +
				"  ?,?,?,?,?,\n" +
				"  ?,?,?\n" +
				")",
				Tuple.of(
						userId,
						user.getLong("role_id"),
						user.getString("username"),
						user.getString("nick_name"),
						password,
						user.getString("phone"),
						user.getString("email"),
						user.getString("sex"),
						user.getString("birthday"),
						user.getString("address"),
						null,
						null,
						user.getString("last_login_time"),
						user.getString("user_desc"),
						user.getInteger("credit_level"),
						LocalDateTime.now(),
						LocalDateTime.now(),
						0
				)).future()
				.onFailure(resultHandler::fail)
				.onSuccess(h->resultHandler.complete(PhResult.ok("OK",userId + "")));
		return this;
	}

	@Override
	public UserService addRole(String roleName, String comment, Promise<PhResult> result) {
		long roleId = idWorker.getId();
		dbWrapper.insert("insert into ph_role(role_id,role_name,comment) values (?,?,?)",
				Tuple.of(
						roleId,
						roleName,
						comment
				)).future()
				.onFailure(result::fail)
				.onSuccess(h->result.complete(PhResult.ok("OK", roleId + "")));
		return this;
	}

	@Override
	public UserService getUserListByRole(Long roleId, Promise<List<JsonObject>> result) {
		dbWrapper.findList("select * from ph_user where role_id = ?", Tuple.of(roleId))
				.future()
				.onFailure(result::fail)
				.onSuccess(result::complete);
		return this;
	}

	@Override
	public UserService updateUser(JsonObject user, Promise<PhResult> resultHandler) {
		Long userId = user.getLong("user_id");
		if (userId == null) {
			resultHandler.fail("用户id不存在");
			return this;
		}
		StringBuilder sb = new StringBuilder();
		Tuple tuple = Tuple.tuple();
		int columnCount = 0;
		sb.append("update ph_user set ");
		Long roleId = user.getLong("role_id");
		if (roleId != null) {
			sb.append("role_id=?,");
			tuple.addLong(roleId);
			columnCount++;
		}
		String username = user.getString("username");
		if (StringUtils.isNotBlank(username)) {
			sb.append("username=?,");
			tuple.addString(username);
			columnCount++;
		}
		String nickName = user.getString("nick_name");
		if (StringUtils.isNotBlank(nickName)) {
			sb.append("nick_name=?,");
			tuple.addString(nickName);
			columnCount++;
		}
		String password = user.getString("password");
		if (StringUtils.isNotBlank(password)) {
			password = BCrypt.hashpw(password, BCrypt.gensalt());
			sb.append("password=?,");
			tuple.addString(password);
			columnCount++;
		}
		String phone = user.getString("phone");
		if (StringUtils.isNotBlank(phone)) {
			sb.append("phone=?,");
			tuple.addString(phone);
			columnCount++;
		}
		String email = user.getString("email");
		if (StringUtils.isNotBlank(email)) {
			sb.append("email=?,");
			tuple.addString(email);
			columnCount++;
		}
		Integer sex = user.getInteger("sex");
		if (sex != null) {
			sb.append("sex=?,");
			tuple.addInteger(sex);
			columnCount++;
		}
		String birthday = user.getString("birthday");
		if (StringUtils.isNotBlank(birthday)) {
			sb.append("birthday=?,");
			tuple.addLocalDate(LocalDate.parse(birthday));
			columnCount++;
		}
		String address = user.getString("address");
		if (StringUtils.isNotBlank(address)) {
			sb.append("address=?,");
			tuple.addString(address);
			columnCount++;
		}
		Long headImg = user.getLong("head_img");
		if (headImg != null) {
			sb.append("head_img=?,");
			tuple.addLong(headImg);
			columnCount++;
		}
		Long backImg = user.getLong("back_img");
		if (backImg != null) {
			sb.append("back_img=?,");
			tuple.addLong(backImg);
			columnCount++;
		}
		String lastLoginTime = user.getString("last_login_time");
		if (StringUtils.isNotBlank(lastLoginTime)) {
			sb.append("last_login_time=?,");
			tuple.addLocalDateTime(LocalDateTime.parse(lastLoginTime));
			columnCount++;
		}
		String userDesc = user.getString("user_desc");
		if (StringUtils.isNotBlank(userDesc)) {
			sb.append("user_desc=?,");
			tuple.addString(userDesc);
			columnCount++;
		}
		Integer creditLevel = user.getInteger("credit_level");
		if (creditLevel != null) {
			sb.append("credit_level=?,");
			tuple.addInteger(creditLevel);
			columnCount++;
		}
		String createTime = user.getString("create_time");
		if (StringUtils.isNotBlank(createTime)) {
			sb.append("create_time=?,");
			tuple.addLocalDateTime(LocalDateTime.parse(createTime));
			columnCount++;
		}
		String updateTime = user.getString("update_time");
		if (StringUtils.isNotBlank(updateTime)) {
			sb.append("update_time=?,");
			tuple.addLocalDateTime(LocalDateTime.parse(updateTime));
			columnCount++;
		}
		if (columnCount == 0) {
			resultHandler.complete(PhResult.ok("没有需要更新的字段"));
			return this;
		}
		//删除最后一个“,”
		sb.deleteCharAt(sb.length() -1);
		sb.append(" where user_id=?");
		tuple.addLong(userId);
		//执行sql
		dbWrapper.update(sb.toString(), tuple)
				.future()
				.onFailure(resultHandler::fail)
				.onSuccess(h->resultHandler.complete(PhResult.ok()));
		return this;
	}

	@Override
	public UserService getUserLastVersion(Long userId, Promise<Long> lastVersionPromise) {
		dbWrapper.findOne("SELECT last_version FROM `ph_user_token` WHERE user_id=?", Tuple.of(userId))
				.future()
				.onFailure(lastVersionPromise::fail)
				.onSuccess(o->{
					if (o.isPresent()) {
						Long lastVersion = o.get().getLong("last_version");
						lastVersionPromise.complete(lastVersion);
					} else {
						lastVersionPromise.complete(0l);
					}
				});
		return null;
	}

	@Override
	public UserService addLoginLog(JsonObject logInfo, Promise<PhResult> resultPromise) {
		long id = idWorker.getId();
		String longitude = logInfo.getString("longitude");
		String latitude = logInfo.getString("latitude");
		Double lng = null;
		Double lat = null;
		if (StringUtils.isNotBlank(longitude) &&
				StringUtils.isNumeric(longitude.replace(".",""))) {
			lng = Double.parseDouble(longitude);

		}
		if (StringUtils.isNotBlank(latitude)
				&& StringUtils.isNumeric(latitude.replace(".",""))) {
			lat = Double.parseDouble(latitude);
		}
		dbWrapper.insert("insert into `ph_login_log` (\n" +
						"    login_id,\n" +
						"    user_id,\n" +
						"    device_id,\n" +
						"    login_time,\n" +
						"    location,\n" +
						"    longitude,\n" +
						"    latitude\n" +
						") values (?,?,?,?,?,?,?)",
				Tuple.of(id,
						logInfo.getLong("user_id"),
						logInfo.getString("device_id"),
						LocalDateTime.now(),
						logInfo.getString("location"),
						lng,
						lat
						)
		).future()
				.onFailure(resultPromise::fail)
				.onSuccess(h->resultPromise.complete(PhResult.ok()));
		return this;
	}



	@Override
	public UserService queryUserByToken(String token, Promise<Boolean> resultPromise){
		dbWrapper.findOne("select token from ph_user_token WHERE token=?", Tuple.of(token))
				.future()
				.onFailure(resultPromise::fail)
				.onSuccess(optional->{
					if (optional.isPresent()) {
						resultPromise.complete(true);
					} else {
						resultPromise.complete(false);
					}
				});
		return this;
	}

}